Object-Oriented Communications with Net.h++

August 15, 1995

© Copyright Rogue Wave Software, 1995

Table of Contents

Introduction

Abstract

Net.h++ is an object-oriented, C++ class library that makes it easier to write applications that communicate across a network. It provides a clean, maintainable way to write code for client/server and peer to peer communications. The sophisticated, object-oriented design allows developers to write code which is both protocol independent and cross platform.

Net.h++ has a layered, modular design. It gives you the ability to write code to a specific protocol (such as TCP/IP), or to a generic Portal Layer that interfaces with a variety of protocols. The Communication Adapter Layer, Portal Layer, and Communication Services Layer work together to provide a flexible, open architecture. Each layer builds on the Communication Adapter Layer providing increasing levels of abstraction. A developer can use any or all of the layers of Net.h++ depending on the requirements of the application.

The initial release of Net.h++ includes a C++ implementation of the Berkeley sockets API _ the defacto standard TCP/IP interface. The Net.h++ Socket Adapter uses the same concepts found in the underlying 'C' API, including sockets, socket addresses, and Internet hosts, but with an intuitive, object-oriented interface. It leverages the most powerful features of C++, including exception handling and templates, without sacrificing any of the functionality of Berkeley sockets. A developer can use the C++ Socket Adapter classes directly or can program to the portable layer that handles communication channel specifics below the interface level. Net.h++ is available in source and object code forms for Unix, Windows NT, and Windows 3.1

Architecture

This section will first define some necessary terms, then describe in some detail the various layers of the Net.h++ architecture.

Definitions and Concepts

Channel

A communication channel is a path of communication between processes. The channel itself is established, maintained, and dismantled by the operating system kernel. API's such as sockets, named pipes, and TLI allow a user level program to access a channel.

We will refer to channels created with different C APIs as different types of channels. For example, a socket channel type is different than a TLI channel type, despite the fact that the kernel creates the same internal data structures for each.

Portal

A portal is an abstraction of an access point to a communications channel. It is maintained by the user level program. In most cases, the operating system returns a portal to a user level program right after creating a channel. The user program can then use the portal to make more portals to the same channel, to send or receive data from the channel, or to control the channel. In some systems (system V streams, e.g.) a portal can be passed from one running program to another. Examples of portals in the C communication APIs are UNIX file descriptors and Windows Winsock handles

The Layers

Net.h++ consists of three architectural layers: the Communication Adapter layer, the Portal layer, and the Communication Services layer. Each layer is built on the one before and offers increasingly abstract services. All three layers are built on a foundation of fundamental datatypes. You can use any or all of the layers of Net.h++, plugging in at the points which satisfy your particular requirements.

Here's a three-D figure showing the architecture:

IMAGE

As you can see, there are four parts to the Net.h++ architecture: the foundation and the three Net.h++ layers. Let's describe each part.

Foundation

Compilers shipping today generally lack the string and container classes that are needed to build a sophisticated product like Net.h++. The Net.h++ Foundation provides these types, along with other services needed by all the layers. As well as simplifying the implementation of Net.h++, the Foundation makes the interface much easier to use and understand.

Most of the Foundation is not actually part of Net.h++ itself: it's Tools.h++, the most popular C++ foundation library available.

Communication Adapter Layer

Each adapter in the Communication Adapters layer is a complete, object oriented, C++ programming interface to a communications system, or a part of a communications system. The communication adapters are designed for maximum power and efficiency. They give you complete control over the communications channel.

Often, a communication adapter encapsulates an existing procedural API, such as BSD sockets or the NetWare C interface. In these cases, the corresponding Net.h++ adapter uses the same concepts and terminology as the procedural API, making the adapter especially easy to use if you have experience with the procedural API. All of the functionality of the procedural interface is preserved. The adapter will be designed to work together with legacy code written using the C API, by providing the ability to "kickdown" from C++ objects to the native data structures.

Portal Layer

The Portal Layer provides a communications interface which can use any communication adapter for its underlying communication channel. This portable interface is used by the Communication Services Layer to provide transport independent services.

Communication Services Layer

The third layer in Net.h++ consists of modules which leverage the Portal Layer to provide transport independent communication services. The services provide functionality at a high level of abstraction. Please see section 3.4 for information on the IOStream module provided in Net.h++ 1.0.

Net.h++ Version 1.0

While the overall architecture of Net.h++ has been designed to accommodate many different network protocols and communication services, Net.h++ version 1.0 is an encapsulation of Berkeley sockets using the TCP and UDP protocols. The Net.h++ sockets implementation includes a Socket Adapter which sits in the Communication Adapter Layer described above. Also included is an IOStream Module which plugs into the Communication Services Layer.

Diagram Conventions

Design patterns use formal notations to denote relationships and interactions between classes and objects. The Net.h++ class diagrams on the following pages depict the static relationships between classes. In the diagram, classes are denoted by a rectangular box with the class name inside. These relationships between classes are defined by the following conventions.

IMAGE

Here is a diagram of the modules that are available in the first release of Net.h++. The diagrams that show up faintly inside the module boxes are the class relationships within and between the modules.

IMAGE

BSD Socket Adapter

The Net.h++ Version 1.0 Communication Adapter Layer consists of a Socket Adapter which models the concepts of sockets directly. The concepts and corresponding classes are as follows:

Figure 1. Net.h++ 1.0 Socket Adapter classes

IMAGE

Socket multiplexing

If you try and read or write to a socket whose buffers are full, the socket will normally block (pause your program until it can complete the operation). This is a problem if we are trying to work with more than one socket at a time. For example, consider a program with two open sockets, s1 and s2, which prints to the screen any input that arrives on a socket. No data is currently available on either socket. If we read from s1, the program will block until data is available on that socket. In the meantime, data could arrive on s2, but we won't get to it. Well, lucky for us, we can.

What we'd really like to do is block on both sockets simultaneously, waiting until data could be read on either s1 or s2. Using Net.h++, you can do exactly that. First, you create a list of socket conditions that you are interested in - that data can be read on s1 or that data can be read on s2. These conditions are represented by instances of the RWSocketAttribute class, the list is a standard Tools.h++ data structure. You can then wait until one of the conditions is true, and handle each condition in turn.

Portal Layer

The Net.h++ version 1.0 Portal Layer includes the RWPortal class, and implementation for socket channels, and an RWSocketPortal class.

Figure 2. Net.h++ Portal Layer

IMAGE

The Portal Layer provides a channel independent portal class, RWPortal, which supports all of the operations performed on portals, except for creation of the channel and creation of the portal. The RWPortal class is a lightweight concrete class. You can use its copy constructor or assignment operator to create new portals to the same communication channel. There are no virtual functions in the RWPortal interface - this allows you to copy a reference to a portal to another portal without changing the semantics of how the portal will behave. This is useful in using a portal as a member of a class, for example.

RWPortal is implemented using the interface-implementation design pattern also used in Rogue Wave's DBTools.h++ class library. Each RWPortal has associated with it an implementation _ an object of a class derived from RWPortalImp. The RWPortalImp object is reference counted and lives on the heap. The RWPortal object is lightweight because the only state it contains is a pointer to its implementation. When a new portal is created from an existing portal using the copy constructor or the assignment operator, the new portal simply references the existing portal's implementation. Polymorphism happens in the implementation object, not in the RWPortal itself; this is why RW Portal does not need any virtual functions. This leads to an efficient design: for a simple send or receive, the only place where a virtual function is called is in the implementation object. All other functions calls are non-virtual, and can even be inlined.

Communications channels are usually used in three stages.

  1. The channel and a portal are established, and options may be set on the channel. This is the connection phase.

  2. The portal is used to send and receive information to and from the channel.

  3. The portal is closed down. If no more portals into the channel are open, then the channel itself is closed down.

    The RWPortal class is used in the 2nd and 3rd phases. The member functions of RW Portal allow sending and receiving of information, and the destructor is responsible for closing down the communication channel if necessary. A generic portal cannot be used for connection establishment, because the semantics of this process vary wildly from one type of channel to another.

    The first phase, connection establishment, is handled by classes derived from RWPortal. These classes work closely with their corresponding classes in the Communication Adapter Layer to provide all the functionality necessary for establishing a connection. Once the connection is established, you may treat the resulting object as an RW Portal. This "slicing" of the portal is acceptable since the part you are slicing off is only needed for establishing a connection.

    A design goal of the RWPortal class is to allow your application to polymorphically use different connection types without having to templatize all of the application code, and all of the communication services, on the specific type of communication channel. This goal can be achieved because generally the only interface which varies a lot between channel types is for the connection phase.

IOStream Module

The IOStreams communication service module lets you program portal input/output using the C++ IOStreams classes. This allows you to use stream insertion and extraction operators to send objects through a communications channel. Using the Rogue Wave virtual streams available in Tools.h++, you can even send complicated data structures through the communication channel and preserve their morphology.

Since the portal class has abstracted the type of channel, we need only one set of iostream classes to attach iostreams to an RWPortal object. The key class in this layer is the RWPortalStreambuf class. RWPortalStreambuf is derived from streambuf and uses the portal as the source and sink for its buffer.

In addition to RWPortalStreambuf, the iostream classes RW PortalOStream and RWPortalIStream are provided. The constructors for these streams require an RWPortal object.

Since RWPortal abstracts away the socket specifics, the prototype IOStream Module does not make use of any socket concepts at all. Instead, the IOStream classes use only the RWPortal class.

Figure 3. Net.h++ Communication Services Layer (IOStream Module)

IMAGE

Comparative Example

As stated earlier, Net.h++ significantly simplifies network programming. The following examples illustrate this point.

This example shows client side code for interaction with a "Greeting" server that will return the greeting that was sent to it from the previous connection. To be fair, we simplified the Berkeley socket's code greatly by using Rogue Wave's RW CString from Tools.h++ (Since everyone already has a copy -- right?). Without the use of RW CString, the Berkeley sockets example would be even uglier!

================ Net.h++ client code ================

#include <iostream.h>
#include <rw/net/sockport.h>
#include <rw/net/inetaddr.h>

main(int argc, char **argv)
{
  RWCString greeting;
  cout << "Type in a greeting: " << flush;
  greeting.readLine(cin);
  try {
    RWSocketPortal port( RWInetAddr(3010,"cold.roguewave.com") );
	port.sendAtLeast( greeting );
    RWCString incoming;
    while ( !(incoming=sock.recv()).isNull() ) {
      cout << incoming << flush;
    }
  }
  catch (const RWxmsg& x) {
    cerr << "Error: " << x.why() << endl;
  }   
  return 0;
}

================ BSD-sockets client code ================

#include <iostream.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>       /* looking for read */
#include <string.h>       /* looking for strlen */
#include <sys/types.h>    /* network includes */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>        /* gethostbyname() */
#include <rw/cstring.h>

const int AL_BUFSIZE = 512;  // a small buffer, so it'll fit on the stack

int main(int argc, char **argv)
{
  RWCString greeting;
  cout << "Type in a greeting: " << flush;
  greeting.readLine(cin);

  // Set up address structure for host connection
  struct hostent *h = gethostbyname("cold.roguewave.com");
  if (h==0) {
    cerr << "Error: Can't find host\n";
    exit(1);
  }
  struct in_addr hostaddr = *(struct in_addr*)*h->h_addr_list;
  struct sockaddr_in addr;
  memset((void*)&addr, 0, sizeof(sockaddr));  // unused bytes must be zero
  addr.sin_family = AF_INET;
  addr.sin_port = htons(3010);
  addr.sin_addr = *(struct in_addr*)*h->h_addr_list;

  // Connect socket to address
  int sock = socket(PF_INET,SOCK_STREAM,0);
  if (sock<0) {
    cerr << "Error: failed to create socket\n";
    exit(1);
  }
  if ( connect(sock,(sockaddr*)&addr,sizeof(addr)) != 0 ) {
    cerr << "Error: failed to connect - error " << errno << endl;
    exit(1);
  }

  // Send message to the socket
  while (!greeting.isNull()) {
    int n = write(sock,greeting.data(),greeting.length());
    if (n>0) {
      greeting.remove(0,n);
    } else {
      cerr << "Failure writing to connection" << endl;
      exit(1);
    }
  }

  // Hear what the socket has to say
  char buf[AL_BUFSIZE+1];
  int n;
  while((n=read(sock,&buf,AL_BUFSIZE))>0) {
    buf[n] = '\0';
    cout << buf << flush;
  }

  return 0;
}

================ Net.h++ server code ================

#include <rw/net/sockport.h>
#include <rw/net/portstrm.h>
#include <rw/net/inetaddr.h>

static RWCString lastGreeting = "It's my first time.  Honest";

main()
{
  RWSocketListener listener( RWInetAddr(3010) );
  for (;;) {
    RWPortal portal = listener();      // get the next incoming connection
    RWPortalIStream strm(portal);
    portal.sendAtLeast(lastGreeting);  // send previous greeting
    lastGreeting.readLine(strm);       // read this client's greeting
  }
  return 0;
}

Benefits Summary

Net.h++ is THE solution for writing network applications in C++. Table 1 shows a summary of benefits and associated features.

Table 1. Summary of Net.h++ Benefits.

TABLETABLE

Net.h++ Abbreviated Public Class Reference

Socket Classes

Classes that relate to the socket specific functionality of Net.h++.

RWSocket

RWSocket is a wrapper for the C concept of a socket. Its member functions correspond exactly with the C functions in the Berkeley sockets API. Typically, RWSocket member functions have the same names as the corresponding C API functions, but the arguments and return values may be different to reflect the C++ environment.

RWSockAddr

RWSockAddr is a proxy to a socket address of some unknown type. RWSockAddr keeps a handle to a reference counted object which is the real address.

RWInetAddr

RWInetAddr is a complete internet address: type information, a host, and a port.

RWInetHost

RWInetHost encapsulates an internet host IP address and its names. You can construct an RW InetHost from either an IP address or a symbolic name.

RWInetPort

RWInetPort encapsulates an internet port and its service names. You can construct an RW InetPort from either an explicit port number or a symbolic service name.

RWInetType

RWInetType is the Internet address type. This class provides a convenient mechanism to construct an RWSockType for an Internet address type.

RWSocketListener

RWSocketListener simplifies creating sockets which listen for incoming connections on a particular address.

RWSocketAttribute

An RWSocketAttribute represents a condition on a particular socket. RW SocketAttributes are used to wait for multiple socket events asynchronously.

Portal Classes

Classes that relate to the Portal Layer of Net.h++.

RWPortal

An RWPortal is an access point of a reliable byte stream communication channel. It is possible for more than one portal to access the communications channel. Portals are implemented using the interface-implementation paradigm. The portal itself is really a handle to an implementation that represents the communication channel.

RWSocketPortal

The RWSocketPortal class provides a socket implementation of Portal build on top of the RW Socket class.

IOStream Classes

Classes that relate to the IOStream Module of Net.h++.

RWPortalStreambuf

An RWPortalStreambuf is an implementation of a iostream streambuf which uses a portal for its source and sink of bytes.

RWPortalIStream

The RWPortalIStream provides an istream which uses the RWPortal as its source of bytes. The RWPortal can be attached to any of the communications channels supported by Net.h++.

The RWPortalOStream

The RWPortalOStream provides an ostream which uses an RWPortal as its sink of bytes. The RWPortal can be attached to any of the communications channels supported by Net.h++.


© Copyright 1995, Rogue Wave Software, Inc.